﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;

namespace Perceiveit.Xml
{
    /// <summary>
    /// Provides a readonly collection of TItem's read from an underlying
    /// XElement sequence.
    /// </summary>
    /// <typeparam name="TItem">The type of items this XEntityCollection contains</typeparam>
    public partial class XEntityCollection<TItem> : XEntityCollectionBase, IEnumerable<TItem>
        where TItem : XEntity
    {

        #region ivars

        /// <summary>
        /// A cached list of items loaded and initialized from the underlying xml
        /// </summary>
        private List<TItem> _cache;

        /// <summary>
        /// A list of xml elements that are known to this lists factory
        /// </summary>
        private List<XElement> _knownchildren;

        /// <summary>
        /// The factory associated with this list to create instances
        /// </summary>
        private XFactory<TItem> _factory;

        #endregion

        //
        // .ctor(s)
        //

        #region public XEntityCollection(XFactory<TItem> factory)

        /// <summary>
        /// Creates a new instance of teh XEntityCollection that uses the 
        /// provided XFactory to construct new instances of type TItem to 
        /// wrap the underlying Xml data
        /// </summary>
        /// <param name="factory">The XFactory to use to construct new instances of type TItem to 
        /// wrap the underlying Xml data - Cannot be null</param>
        public XEntityCollection(XFactory<TItem> factory)
        {
            if (null == factory)
                throw new ArgumentNullException("factory");

            this._factory = factory;
        }

        #endregion

        //
        // public properties
        //

        #region public int Count {get;}

        /// <summary>
        /// Gets the number of items in this collection
        /// </summary>
        public int Count
        {
            get
            {
                if (null == _knownchildren)
                    return 0;
                else
                    return _knownchildren.Count;
            }
        }

        #endregion

        #region public virtual TItem this[int index] {get; set - not supported}

        /// <summary>
        /// Gets the TItem at the specified index. 
        /// NOTE: the settter is defined but will throw an exception on read-only classes
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        public virtual TItem this[int index]
        {
            get { return this.GetItemAt(index); }

            //the only way .NET supports a setter on the inheriting class
            //is to provide an implementation on the base class. 
            //It should no be like this - generated as independant methods anyway
            set { throw new NotSupportedException("This collection is readonly"); }
        }

        #endregion

        #region public virtual bool IsReadOnly {get;}

        /// <summary>
        /// Gets the flag to identify if this
        /// collection is read only (cannot be set, removed or added to)
        /// </summary>
        /// <remarks>The default implementation returns true. 
        /// Inheritors can override this to return their own values</remarks>
        public virtual bool IsReadOnly
        {
            get { return true; }
        }

        #endregion


        //
        // protected properties
        //
        

        protected IList<TItem> Cache
        {
            get { return _cache; }
        }

        protected IList<XElement> KnownChildren
        {
            get { return _knownchildren; }
        }

        protected internal XFactory<TItem> Factory
        {
            get { return _factory; }
        }

        //
        // protected implementation methods
        //


        protected override void OnInitComplete()
        {
            base.OnInitComplete();
            this.ResetCollection();
        }

        protected void ResetCollection()
        {
            this.RegisterCollectionChanged();
            if (_cache == null || _cache.Count > 0)
            {
                _cache = new List<TItem>();
            }
            if (_knownchildren == null || _knownchildren.Count > 0)
            {
                _knownchildren = new List<XElement>();
            }
            if (this.HasExistingNode)
            {
                PopulateKnownChildren();
            }
            
        }

        protected virtual void PopulateKnownChildren()
        {
            IEnumerable<XElement> all = this.Element.Elements();
            XFactory<TItem> factory;
            foreach (XElement ele in all)
            {
                if (this.Factory.IsKnownName(ele.Name, out factory))
                {
                    _knownchildren.Add(ele);
                }
            }
        }

        protected TItem GetItemAt(int index)
        {
            if (index >= this.Count)
                throw new ArgumentOutOfRangeException("index");
            TItem value;
            if (this.TryGetCachedItem(index, out value) == false)
            {
                XElement ele = this.KnownChildren[index];
                value = this.CreateEntityFromChild(ele);
                this.InsertCachedItem(index, value);
            }
            return value;
        }


        protected virtual TItem CreateEntityFromChild(XElement child)
        {
            TItem entity = this.Factory.GetEntityForName(child.Name);
            if (null != entity)
                entity.Init(child);
            return entity;
        }

        protected virtual void InsertCachedItem(int index, TItem value)
        {
            while (this.Cache.Count <= index)
                Cache.Add(null);
            this.Cache[index] = value;
        }

        protected virtual bool TryGetCachedItem(int index, out TItem item)
        {
            if (index >= this.Cache.Count)
            {
                item = null;
                return false;
            }
            else
            {
                item = this.Cache[index];
                return null != item;
            }
        }

        protected void RemoveCachedItem(TItem item)
        {
            this.Cache.Remove(item);
        }

        protected void RemoveCachedItemAt(int index)
        {
            this.Cache.RemoveAt(index);
        }

        //
        // interface implementations
        //

        #region IEnumerable<TItem>

        /// <summary>
        /// Gets a new enumerator to enumerate over the entire collection of 
        /// TItem's contained in this instance.
        /// </summary>
        /// <returns>A new enumerator for this collection</returns>
        public IEnumerator<TItem> GetEnumerator()
        {
            return new XEntityCollectionEnumerator(this);
        }

        #endregion

        #region IEnumerable

        /// <summary>
        /// Explicit interface implementation of the IEnumerable.GetEnumerator() method
        /// </summary>
        /// <returns>A new enumerator for this collection</returns>
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }

        #endregion

    }
}
